package com.clear.honghefinance.biz.modules.system.member.service.impl;

import com.clear.honghefinance.biz.constant.RedisKeyConstant;
import com.clear.honghefinance.biz.modules.system.member.converter.ObjectConvertor;
import com.clear.honghefinance.biz.modules.system.member.domain.entity.MemberBindPhone;
import com.clear.honghefinance.biz.modules.system.member.domain.entity.MemberBindWxOpenId;
import com.clear.honghefinance.biz.modules.system.member.domain.vo.AdminDTO;
import com.clear.honghefinance.biz.modules.system.member.domain.dto.PhoneRegisterDTO;
import com.clear.honghefinance.biz.modules.system.member.domain.vo.GenerateMpRegCodeVO;
import com.clear.honghefinance.biz.modules.system.member.enums.SmsCodeTypeEnum;
import com.clear.honghefinance.biz.modules.system.member.service.*;
import com.clear.honghefinance.biz.modules.system.tenant.service.ITenantService;
import com.clear.honghefinance.common.dto.vo.TokenResponse;
import com.clear.honghefinance.common.exception.BizException;
import com.clear.honghefinance.common.exception.ParameterException;
import com.clear.honghefinance.common.service.TokenService;
import com.clear.honghefinance.common.util.redis.RedisUtil;
import com.clear.honghefinance.wechat.config.wechat.WechatConfig;
import com.clear.honghefinance.wechat.dto.req.GenerateMpQrcodeDTO;
import com.clear.honghefinance.wechat.dto.resp.AccessTokenVO;
import com.clear.honghefinance.wechat.dto.resp.GenerateMpQrcodeVO;
import com.clear.honghefinance.wechat.service.WechatService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class MemberRegServiceImpl implements IMemberRegisterService {

    @Resource
    private MemberLoginService memberLoginService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private MemberBindPhoneService memberBindPhoneService;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private ITenantService tenantService;
    @Resource
    private IMemberService memberService;
    @Resource
    private WechatService wechatService;
    @Resource
    private WechatConfig wechatConfig;
    @Resource
    private IMemberBindWxOpenidService memberBindWxOpenidService;
    @Resource
    private TokenService adminTokenService;

    @Override
    public long phoneRegister(PhoneRegisterDTO dto) {
        if (!Objects.equals(dto.getPassword(), dto.getConfirmPassword())) {
            throw new ParameterException("两次输入的密码不一致");
        }
        // 校验短信验证码
        memberLoginService.checkSmsCode(dto.getPhone(), dto.getSmsCode(), SmsCodeTypeEnum.REGISTER.getCode());

        RLock rLock = redissonClient.getLock(redisUtil.buildKey(RedisKeyConstant.PHONE_REGISTER, dto.getPhone()));
        try {
            rLock.lock();
            MemberBindPhone memberByPhone = memberBindPhoneService.getMemberByPhone(dto.getPhone());
            if (Objects.nonNull(memberByPhone)) {
                log.warn("手机号：{} 已注册", dto.getPhone());
                throw new BizException("手机号已注册");
            }
            // 编程式事务
            Long resMemberId = transactionTemplate.execute(transactionStatus -> {
                // 1.租户表
                long tenantId = tenantService.add();
                // 2.会员表
                long memberId = memberService.register(tenantId);
                if (memberId <= 0) {
                    throw new BizException("注册异常");
                }
                // 3.会员绑定手机表
                memberBindPhoneService.register(dto.getPhone(), dto.getPassword(), memberId);
                return memberId;
            });
            if (Objects.isNull(resMemberId)) {
                throw new BizException("注册失败");
            }
            return resMemberId;
        } catch (Exception e) {
            throw new BizException("注册失败", e);
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 生成微信公众号二维码（关注微信公众号注册）
     *
     * @param clientId
     * @return
     */
    @Override
    public GenerateMpRegCodeVO generateMpRegCode(String clientId) {
        // 获取微信公众号Access Token
//        AccessTokenVO mpAccessTokenVO = wechatService.getMpAccessToken(wechatConfig.getMp().getAppId(), wechatConfig.getMp().getSecret());
        // == 获取微信公众号Access Token 查缓存 ==
        AccessTokenVO mpAccessTokenVO = wechatService.getMpAccessTokenByCache(wechatConfig.getMp().getAppId());

        GenerateMpQrcodeDTO dto = new GenerateMpQrcodeDTO();
        dto.setExpireSeconds(wechatConfig.getMp().getCodeExpire());
        dto.setActionName("QR_STR_SCENE");
        dto.setActionInfo(dto.new ActionInfo());
        dto.getActionInfo().setScene(dto.new Scene());
        // 场景值ID：ScanReg_appId_clientId
        dto.getActionInfo().getScene().setSceneStr("ScanReg_" + wechatConfig.getMp().getAppId() + "_" + clientId);
        if (log.isInfoEnabled()) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                log.info("生成微信公众号临时二维码，入参:{}", objectMapper.writeValueAsString(dto));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        // 生成微信公众号临时二维码
        GenerateMpQrcodeVO vo = wechatService.generateMpQrcodeCreate(mpAccessTokenVO.getAccessToken(), dto);

        // 对象转换
        return ObjectConvertor.INSTANCE.convertGenerateMpQrcodeVOToGenerateMpRegCodeVO(vo);
    }

    /**
     * 处理微信公众号事件回调（监听）
     *
     * @param map
     */
    @Override
    public void handleMpSubscribeEventRequest(Map<String, String> map) {
        log.info("监听到消息:{}", new Gson().toJson(map));
        // todo if-else可优化为策略模式
        if ("subscribe".equals(map.get("Event")) && StringUtils.isNotBlank(map.get("EventKey"))) {
            //  EventKey=qrscene_ScanReg_wx347b3b2a761d0276_dcafeac6f9d941c5a96f97d69783e54c, Event=subscribe
            // a.解析EventKey
            // 提示：在生成带场景二维码时：scene_str（场景值ID）：ScanReg_appId_clientId
            String[] split = map.get("EventKey").split("_");
            String appId = split[2];
            String clientId = split[3];
            if ("qrscene".equals(split[0]) && "ScanReg".equals(split[1])) {
                String openId = map.get("FromUserName");
                this.registerByMpOpenId(appId, clientId, openId);
            }
        } else if ("SCAN".equals(map.get("Event")) && StringUtils.isNotBlank(map.get("EventKey"))) {
            // EventKey=ScanReg_wx347b3b2a761d0276_dcafeac6f9d941c5a96f97d69783e54c
            String[] split = map.get("EventKey").split("_");
            String appId = split[1];
            String clientId = split[2];
            if ("ScanReg".equals(split[0])) {
                String openId = map.get("FromUserName");
                this.registerByMpOpenId(appId, clientId, openId);
            }
        }
    }

    /**
     * 根据openId注册、注册即登录
     *
     * @param appId
     * @param clientId 客户端id 用户构建token缓存key
     * @param openId
     * @return
     */
    private TokenResponse registerByMpOpenId(String appId, String clientId, String openId) {
        log.info("根据openId注册（登录） appId:{}, clientId={}, openId={}", appId, clientId, openId);
        // 扫描注册
        long memberId = scanRegister(appId, openId);
        // 写入token信息到redis
        AdminDTO adminDTO = new AdminDTO();
        adminDTO.setId(memberId);
        // todo 这应该也存入角色id

        // 生成token
        adminTokenService.setToken(adminDTO);

        // token存入redis
        String key = redisUtil.buildKey(RedisKeyConstant.CLIENT_TOKEN_KEY, clientId);
        redisUtil.setNx(key, new Gson().toJson(adminDTO.getToken()), 10L, TimeUnit.MINUTES);
        return adminDTO.getToken();
    }

    /**
     * 扫描注册
     *
     * @param appId
     * @param openId
     * @return
     */
    private long scanRegister(String appId, String openId) {
        // 查询用户是否以注册
        MemberBindWxOpenId memberBindWxOpenId = memberBindWxOpenidService.get(appId, openId);
        if (Objects.nonNull(memberBindWxOpenId)) {
            // 用户存在，返回用户id
            return memberBindWxOpenId.getMemberId();
        }

        // 编程式事务
        Long resMemberId = transactionTemplate.execute(transactionStatus -> {
            // a.租户表
            long tenantId = tenantService.add();
            // b.会员表、用户表
            long memberId = memberService.register(tenantId);
            memberBindWxOpenidService.register(appId, openId, memberId);
            return memberId;
        });
        if (Objects.isNull(resMemberId)) {
            throw new BizException("注册失败");
        }
        return resMemberId;
    }

}